#include "stdafx.h"

#include "AtomContentHandler.h"
#include "StickyTweetsAppXP.h"

AtomContentHandler::AtomContentHandler(CAtlList<TwitterStatus>& timelineUI) :
  _twitterElement(TwitterElement_None),
  _inUser(FALSE),
  _timelineUI(timelineUI)
{
}

AtomContentHandler::~AtomContentHandler()
{
}

HRESULT AtomContentHandler::startElement( 
            /* [in] */ const wchar_t __RPC_FAR *pwchNamespaceUri,
            /* [in] */ int cchNamespaceUri,
            /* [in] */ const wchar_t __RPC_FAR *pwchLocalName,
            /* [in] */ int cchLocalName,
            /* [in] */ const wchar_t __RPC_FAR *pwchRawName,
            /* [in] */ int cchRawName,
            /* [in] */ ISAXAttributes __RPC_FAR *pAttributes)
{
  if (wcscmp(pwchLocalName, L"published") == 0 && !_inUser)
  {
    _twitterElement = TwitterElement_Date;
    _date = L"";
  }
  else if (wcscmp(pwchLocalName, L"id") == 0 && !_inUser)
  {
    // We will get the feed's id but the first entry's id will overwrite
    _twitterElement = TwitterElement_Id;
    _id = L"";
  }
  else if (wcscmp(pwchLocalName, L"title") == 0)
  {
    _twitterElement = TwitterElement_Text;
    _text = L"";
  }
  else if (wcscmp(pwchRawName, L"twitter:source") == 0 && !_inUser)
  {
    // For Atom, this is an actual HTML anchor element, we will have to parse it
    _twitterElement = TwitterElement_Source;
    _source = L"";
  }
  else if (wcscmp(pwchLocalName, L"author") == 0)
  {
    _inUser = TRUE;
  }
  else if (wcscmp(pwchLocalName, L"name") == 0)
  {
    // We might want to get this from the entry's link instead
    _twitterElement = TwitterElement_ScreenName;
    _screenName = L"";
  }
  else if (wcscmp(pwchLocalName, L"link") == 0)
  {
    // We will need to verify that rel="image" and then grab the href attribute
    const wchar_t* pValue;
    int valueLen;

    HRESULT hr = pAttributes->getValueFromName(L"", 0, L"rel", 3, &pValue, &valueLen);
    if (SUCCEEDED(hr) && wcsncmp(pValue, L"image", valueLen) != 0)
      hr = E_FAIL;

    if (SUCCEEDED(hr))
      hr = pAttributes->getValueFromName(L"", 0, L"href", 4, &pValue, &valueLen);

    if (SUCCEEDED(hr))
    {
      _profileImageUrl.Empty();
      _profileImageUrl.Append(pValue, valueLen);
    }
  }
  else
  {
    _twitterElement = TwitterElement_None;
  }

  return S_OK;
}

HRESULT AtomContentHandler::characters(const wchar_t* pwchChars, int cchChars)
{
  switch (_twitterElement)
  {
  case TwitterElement_Date:
    _date.Append(pwchChars, cchChars);
    break;

  case TwitterElement_Id:
    _id.Append(pwchChars, cchChars);
    break;

  case TwitterElement_Text:
    _text.Append(pwchChars, cchChars);
    break;

  case TwitterElement_Source:
    _source.Append(pwchChars, cchChars);
    break;

  case TwitterElement_ScreenName:
    _screenName.Append(pwchChars, cchChars);
    break;

  case TwitterElement_ProfileImageUrl:
    _profileImageUrl.Append(pwchChars, cchChars);
    break;
  }

  return S_OK;
}

HRESULT AtomContentHandler::endElement( 
            /* [in] */ const wchar_t *pwchNamespaceUri,
            /* [in] */ int cchNamespaceUri,
            /* [in] */ const wchar_t *pwchLocalName,
            /* [in] */ int cchLocalName,
            /* [in] */ const wchar_t *pwchRawName,
            /* [in] */ int cchRawName)
{
  _twitterElement = TwitterElement_None;
  if (wcscmp(pwchLocalName, L"entry") == 0)
  {
    StickyTweetsAppXP& app = StickyTweetsAppXP::GetInstance();

    int idIdx = _id.ReverseFind(L':');
    if (idIdx >= 0)
    {
      _id = _id.Right(_id.GetLength() - (idIdx + 1));
    }

    int compare(0);
    POSITION insertPos(NULL);
    POSITION testPos = _timelineUI.GetHeadPosition();
    while (testPos)
    {
      insertPos = testPos;
      TwitterStatus& compStat = _timelineUI.GetNext(testPos);
      if (_id == compStat.Id)
      {
        //ATLTRACE2(L"Status %s already exisits in list\n", _id);
        return S_OK;
      }

      if (_id.GetLength() < compStat.Id.GetLength() || _id < compStat.Id)
      {
        //ATLTRACE2(L"Insert status %s after %s\n", _id, compStat.Id);
        compare = 1;
      }
      else if (_id.GetLength() > compStat.Id.GetLength() || _id > compStat.Id)
      {
        //ATLTRACE2(L"Insert status %s before %s\n", _id, compStat.Id);
        compare = -1;
        break;
      }
    }

    TwitterStatus status;
    status.CreatedAt.Text = app.FormatDate(_date);
    status.Id = _id;

    status.LinkCount = 0;
    //ATLTRACE2(L"Tweet text: [%s]\n", _text.GetString());
    _text.Replace('\n', ' ');
    _text.Replace(L"&quot;", L"\"");
    _text.Replace(L"&lt;", L"<");
    _text.Replace(L"&gt;", L">");

    // TODO: This is slow but works, fix above code (stack warning?!?)
    if (_text.Find(L"&#x") > -1)
    {
      CString unicodeChar;
      for (TCHAR i = 128; i < 256; i++)
      {
        unicodeChar.Format(L"&#x%02x;", i);
        _text.Replace(unicodeChar, CString(i));
      }
      for (TCHAR i = 128; i < 2048; i++)
      {
        unicodeChar.Format(L"&#x%04x;", i);
        _text.Replace(unicodeChar, CString(i));
      }
    }

    status.FullText = _text;

    int firstChar(0), tokStart(0);

    status.NumWords = 0;
    CString word = status.FullText.Tokenize(L" ", tokStart);
    while (word.GetLength() > 0)
    {
      int inWordLinkIdx = max(word.Find(L"@"), word.Find(L"http"));
      if (inWordLinkIdx > 0)
      {
        status.Words[status.NumWords].Link = NoLink;
        status.Words[status.NumWords].Length = inWordLinkIdx;
        status.Words[status.NumWords].First = firstChar;
        firstChar += inWordLinkIdx;
        status.NumWords++;
        word = word.Mid(inWordLinkIdx);
        continue;
      }

      CString invalidLinkChars;

      if (word.Left(1) == L"@" && word.GetLength() > 1)
      {
        status.Words[status.NumWords].Link = UserLink;
        invalidLinkChars = L"?'()!:.,\"";
      }
      else if (word.Left(4) == L"http")
      {
        status.Words[status.NumWords].Link = WebLink;
        invalidLinkChars = L"'()!,";
      }
      else
      {
        status.Words[status.NumWords].Link = NoLink;
      }

      while (status.FullText.GetAt(firstChar) != word.GetAt(0))
        firstChar++;
      status.Words[status.NumWords].First = firstChar;

      if (status.Words[status.NumWords].Link != NoLink)
      {
        int invalidIdx = word.FindOneOf(invalidLinkChars);
        if (invalidIdx >= 0)
        {
          status.Words[status.NumWords].Length = invalidIdx;
          status.NumWords++;
          status.Words[status.NumWords].Link = NoLink;
          status.Words[status.NumWords].First = firstChar + invalidIdx;
          status.Words[status.NumWords].Length = (word.GetLength() + 1) - invalidIdx;
        }
        else
        {
          status.Words[status.NumWords].Length = word.GetLength() + 1;
        }
      }
      else
      {
        status.Words[status.NumWords].Length = word.GetLength() + 1;
      }

      firstChar = tokStart;
      status.NumWords++;
      word = status.FullText.Tokenize(L" ", tokStart);
    }

    if (status.NumWords > 0)
      status.Words[status.NumWords - 1].Length--;

    wregex sourceFinder(L"<a.*>(.+)</a>");
    wcmatch sourceMatch;

    if (regex_match(_source.GetString(), sourceMatch, sourceFinder))
    {
      if (sourceMatch.size() >= 2)
        status.Source.Text = CString(L" from ") + _source.Mid(sourceMatch.position(1), sourceMatch.length(1));
      else
        status.Source.Text = L" from unknown";
    }
    else
    {
      status.Source.Text = CString(L" from ") + _source;
    }

    status.User.ScreenName.Text = _screenName.Left(_screenName.Find(L" "));
    status.User.ProfileImageUrl.Text = _profileImageUrl;

    if (insertPos == NULL)
      _timelineUI.AddHead(status);
    else if (compare < 0)
      _timelineUI.InsertBefore(insertPos, status);
    else if (compare > 0)
      _timelineUI.InsertAfter(insertPos, status);
    return S_OK;
  }
  else if (wcscmp(pwchLocalName, L"author") == 0)
  {
    _inUser = FALSE;
  }

  return S_OK;
}

